#include "ET++.ph"
#include "Converter.h"

#include "Class.h"
#include "Data.h"
#include "Bitmap.h"
#include "DevBitmap.h"
#include "String.h"
#include "ColorMapper.h"
#include "TypeMatcher.h"
#include "ET_stdio.h"

static const Symbol cDocTypePBM("PBM");

//---- PBMMatcher --------------------------------------------------------------

class PBMMatcher: public TypeMatcher {
public:
	PBMMatcher(const Symbol &t) : TypeMatcher(t, 0, 0, FALSE)
		{ }
	bool MatchContents(Data*, const char *buf, int len)
		{ return len >= 2 && buf[0] == 'P' && buf[1] >= '1' && buf[1] <= '6'; }
	bool MatchExtension(Data*, const char *e)
		{ return strlen(e) == 3 && e[0] == 'p' && e[2] == 'm'
			&& (e[1] == 'b' || e[1] == 'g' || e[1] == 'p' || e[1] == 'n'); }
	Bitmap *GetBitmap()
		{ return gImageIcon; }
};

static PBMMatcher pbmmatcher(cDocTypePBM);

//---- PbmConverter ------------------------------------------------------------

class PbmConverter : public Converter {
public:
	MetaDef(PbmConverter);
	PbmConverter()
		{ }
	const char *AsString()
		{ return "Portable Bitmap (PBM)"; }
	char *DoConvert(IStream &is, Object **);
	Object *Convert(Data *data, Class *want);
	bool CanConvert(Data *data, Class *want)
		{ return data->Type() == cDocTypePBM && want == Meta(Bitmap); }
	bool CanConvertTo(Object *op)
		{ return op->IsKindOf(Bitmap); }
	bool ConvertTo(Data *data, Object *op);
};

NewMetaImpl0(PbmConverter,Converter);

// utils -----------------------------------------------------------------------

static int IntTable[256];
static unsigned int Initialized= 0;

#define NOTINT  -1
#define COMMENT -2
#define SPACE   -3
#define NEWLINE -4

static int pbmReadChar(IStream &is)
{
	byte c;

	is.get(c);
	if (is.eof())
		return -1;

	if (IntTable[c] == COMMENT) {
		do {
			is.get(c);
			if (is.eof())
				return -1;
		} while (IntTable[c] != NEWLINE);
	}
	return c;
}

static int pbmReadInt(IStream &is)
{
	int c, value;

	for (;;) {
		c= pbmReadChar(is);
		if (c < 0)
			return -1;
		if (IntTable[c] >= 0)
			break;
	}

	value= IntTable[c];
	for (;;) {
		c= pbmReadChar(is);
		if (c < 0)
			return -1;
		if (IntTable[c] < 0)
			return value;
		value= (value * 10) + IntTable[c];
	}
}

char *PbmConverter::DoConvert(IStream &is, Object **op)
{
	Bitmap *BM;
	register DevBitmap *dbm;
	int  x, y, width, height, linelen, depth;
	int src, components, maxval= 1, bitsperpixel= 1;
	byte buf[4], *row= 0, *rp;
	bool raw, readmax;
	RGB rgb;

	if (! Initialized) {
		unsigned int a;

		for (a= 0; a < 256; a++)
			IntTable[a]= NOTINT;
		IntTable['#']= COMMENT;
		IntTable['\n']= NEWLINE;
		IntTable['\r']= IntTable['\t']= IntTable[' ']= SPACE;
		IntTable['0']= 0;
		IntTable['1']= 1;
		IntTable['2']= 2;
		IntTable['3']= 3;
		IntTable['4']= 4;
		IntTable['5']= 5;
		IntTable['6']= 6;
		IntTable['7']= 7;
		IntTable['8']= 8;
		IntTable['9']= 9;
		Initialized= 1;
	}

	if (is.read(buf, 2) != 2)
		return "file too short";

	if (buf[0] != 'P')
		return "not a pbm file";

	switch (buf[1]) {
	case '1':
		readmax= FALSE;
		components= 1;
		raw= FALSE;
		break;
	case '2':
		readmax= TRUE;
		components= 1;
		raw= FALSE;
		break;
	case '3':
		readmax= TRUE;
		components= 3;
		raw= FALSE;
		break;
	case '4':
		readmax= FALSE;
		components= 1;
		raw= TRUE;
		break;
	case '5':
		readmax= TRUE;
		components= 1;
		raw= TRUE;
		break;
	case '6':
		readmax= TRUE;
		components= 3;
		raw= TRUE;
		break;
	default:
		return "not a pbm file";
	}
		
	if ((width= pbmReadInt(is)) < 0)
		return "wrong width";
				
	if ((height= pbmReadInt(is)) < 0)
		return "wrong height";
				
	if (readmax && (maxval= pbmReadInt(is)) < 0)
		return "wrong maxval";

	if (maxval == 1)
		bitsperpixel= 1;
	else if (maxval < 16)
		bitsperpixel= 4;
	else if (maxval < 256)
		bitsperpixel= 8;
	else
		return "too big maxval";

	depth= bitsperpixel * components;

	BM= new Bitmap(Point(width, height), depth);
	dbm= BM->GetDevBitmap();

	if (buf[1] == '2' || buf[1] == '5') {
		for (int i= 0; i <= maxval; i++) {
			rgb.red= rgb.green= rgb.blue= (255 * i) / maxval;
			BM->ChangeRGB(i, &rgb);
		}
	}

	switch (buf[1]) {

	case '1':   // normal PBM
		for (y= 0; y < height; y++) {
			for (x= 0; x < width; x++) {
				do {
					if ((src= pbmReadChar(is)) < 0)
						return "Short image";
					if (IntTable[src] == NOTINT)
						return "Bad image data";
				} while (IntTable[src] < 0);

				switch (IntTable[src]) {
				case 1:
					dbm->SetPixel(x, y, 1);
					break;
				case 0:
					dbm->SetPixel(x, y, 0);
					break;
				default:
					return "pixel value out of range";
				}
			}
		}
		break;

	case '2':   // normal PGM
		for (y= 0; y < height; y++) {
			for (x= 0; x < width; x++) {
				src= pbmReadInt(is);
				if (src < 0 || src > maxval)
					return "pixel value out of range";
				dbm->SetPixel(x, y, (src*255)/maxval);
			}
		}
		break;

	case '3':   // normal PPM
		for (y= 0; y < height; y++) {
			for (x= 0; x < width; x++) {
				if ((src= pbmReadInt(is)) < 0 || src > maxval)
					return "pixel value out of range";
				rgb.red= (src*255)/maxval;
				if ((src= pbmReadInt(is)) < 0 || src > maxval)
					return "pixel value out of range";
				rgb.green= (src*255)/maxval;
				if ((src= pbmReadInt(is)) < 0 || src > maxval)
					return "pixel value out of range";
				rgb.blue= (src*255)/maxval;
				BM->SetRGB(x, y, &rgb);
			}
		}
		break;

	case '4':   // raw PBM
		linelen= (width+7)/8;
		row= new byte[linelen];
		{
			BitmapInfo bi(Point(width, height), 1, 1);
			for (y= 0; y < height; y++) {
				if (is.read(row, linelen) != linelen) {
					delete row;
					return "Short image";
				}
				dbm->SetRow(&bi, y, row);
			}
		}
		break;

	case '5':   // raw PGM
		linelen= width;
		row= new byte[linelen];
		{
			BitmapInfo bi(Point(width, height), 8, 1);
			for (y= 0; y < height; y++) {
				if (is.read(row, linelen) != linelen) {
					delete row;
					return "Short image";
				}
				rp= row;
				for (x= 0; x < width; x++)
					*rp++= (*rp * 255)/maxval;
				dbm->SetRow(&bi, y, row);
			}
		}
		break;
		
	case '6':   // raw PPM
		linelen= width*3;
		row= new byte[linelen];
		for (y= 0; y < height; y++) {
			if (is.read(row, linelen) != linelen) {
				delete row;
				return "Short image";
			}
			rp= row;
			for (x= 0; x < width; x++) {
				rgb.red=   (*rp++ * 255)/maxval;
				rgb.green= (*rp++ * 255)/maxval;
				rgb.blue=  (*rp++ * 255)/maxval;
				BM->SetRGB(x, y, &rgb);
			}
		}
		break;

	}
	SafeDelete(row);
	*op= BM;
	return 0;
}

Object *PbmConverter::Convert(Data *data, Class*)
{
	Object *op= 0;
	IStream is(data->GetStreamBufForReading(), 0, TRUE);
	char *msg= DoConvert(is, &op);
	if (msg)
		fprintf(stderr, "PbmConverter::Convert: %s\n", msg);
	return op;
}

bool PbmConverter::ConvertTo(Data *data, Object *op)
{
	register int x, y;
	Bitmap *bm= (Bitmap*) op;
	register DevBitmap *dbm= bm->GetDevBitmap();
	Point e= bm->Size();
	RGB rgb;
	int rowlen;
	char magic;
	byte *row= 0, *rp;
	OStream ois(data->GetStreamBufForWriting(), TRUE);

	if (dbm->Depth() == 1) {
		magic= '4';
		rowlen= (e.x+7)/8;
	} else if (bm->GetColorMapper()->IsGrey()) {
		magic= '5';
		rowlen= e.x;
	} else {
		magic= '6';
		rowlen= e.x*3;
	}

	ois << "P" << magic NL;
	ois << e.x SP << e.y NL;
	if (magic != '4')
		ois << 255 NL;

	row= new byte[rowlen];

	for (y= 0; y < e.y; y++) {
		rp= row;
		switch (magic) {
		case '4':
			{
				BitmapInfo bi(e, 1, 1);
				dbm->GetRow(&bi, y, row);
			}
			break;
		case '5':
			for (x= 0; x < e.x; x++) {
				bm->GetRGB(x, y, &rgb);
				*rp++= rgb.red;
			}
			break;
		case '6':
			for (x= 0; x < e.x; x++) {
				bm->GetRGB(x, y, &rgb);
				*rp++= rgb.red;
				*rp++= rgb.green;
				*rp++= rgb.blue;
			}
			break;
		}
		if (ois.write(row, rowlen) != rowlen) {
			fprintf(stderr, "PbmConverter::ConvertTo: can't write\n");
			break;
		}
	}
	SafeDelete(row);

	return TRUE;
}
